use actix_web::{post, Responder, web};
use actix_web::web::Bytes;
use bytestring::ByteString;
use futures_util::StreamExt;
use log::debug;
use serde::Serialize;

use common::Error;
use crate::state::StateRef;

#[derive(Serialize)]
pub enum WriteResponse {
    Success(WriteResult),
    Fail(String),
}

#[derive(Serialize)]
pub struct WriteResult {
    success: u64,
    fail: u64,
}

impl WriteResponse {
    fn ok(success: u64, fail: u64) -> Self {
        Self::Success(WriteResult { success, fail })
    }
}


const MAX_SIZE: usize = 262_144; // max payload size is 256k

#[post("/write")]
async fn batch_write(mut payload: web::Payload, state: web::Data<StateRef>) -> std::io::Result<impl Responder> {
    let mut body = web::BytesMut::new();
    while let Some(chunk) = payload.next().await {
        let chunk = chunk.map_err(|e| {
            Error::NetworkErr(format!("Network error: {:?}", e)).to_io_error()
        })?;
        // limit max size of in-memory payload
        if (body.len() + chunk.len()) > MAX_SIZE {
            return Err(Error::NetworkErr("Package out of 256k".to_string()).to_io_error());
        }
        body.extend_from_slice(&chunk);
    }
    let result = Bytes::from(body);
    let bs = ByteString::try_from(result).map_err(|e| {
        Error::NetworkErr(format!("write payload is not valid Utf8: {:?}", e)).to_io_error()
    })?;
    let mut success = 0;
    let mut fail = 0;
    for line in bs.lines() {
        let line = line.trim();
        if !line.is_empty() && !is_blank(line) {
            debug!(target: "http", "write single line: {}", line);
            // state
            let db = &state.db;
            // TODO, avoid copy line
            if let Ok(()) = db.insert(ByteString::from(line)).await {
                success += 1;
            } else {
                fail += 1;
            }
        } else {
            debug!(target: "http", "skip blank line");
        }
    }
    Ok(web::Json(WriteResponse::ok(success, fail)))
}

#[inline]
fn is_blank(s: &str) -> bool {
    s.chars().all(|c|c.is_whitespace())
}